// Copyright (C) 2010 Choonghwan Lee (linjus.lee@gmail.com)
//
// This file is part of amy (http://unitcvt.codeplex.com/).
//
// amy is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// amy is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with amy. If not, see <http://www.gnu.org/licenses/>.


function UnitValue(unit) {
	this.unit = unit;
	
	this.value = 0;
	this.disabled = false;
	
	this.altvalue = 0;
	this.widget = null;
}

function Postfix() {
}

Postfix.prototype.assert = function(expr, msg, args) {
	Mojo.assert(expr, msg, args);
}

Postfix.prototype.inversePartial = function(stack, tokens, value) {
	for (var i = 0; i < tokens.length; ++i) {
		var token = tokens[i];
		if (this.isValueToken(token))
			stack.push(token);
		else if (this.isOperatorToken(token)) {
			// Constants can be immediately evaluated
			if (!this.isUnitTokenOperator(stack))
				this.evaluateOperator(stack, token);
			else
				stack.push(token);
		}
		else {
			// Assume this is always the SI value
		}
	}

	var curval = value;
	while (stack.length > 0) {
		var token = stack.pop();
		if (this.isOperatorToken(token)) {
			curval = this.evaluateInverseOperator(stack, token, curval);
		}
	}
	this.assert(stack.length == 0, "unexpected result #{stacklength}", { stacklength: stack.length });
	stack.push(curval);
}

Postfix.prototype.inverse = function(expr, value) {
	var tokens = this.tokenize(expr);
	var stack = new Array();
	
	var parts = new Array();
	var i = 0;
	for (var j = 0; j < tokens.length; ++j) {
		if (this.isSeperatorToken(tokens[j])) {
			var part = new Object();
			part.expr = new Array();
			part.separator = tokens[j + 1];
			for (var k = i; k < j; ++k)
				part.expr.push(tokens[k]);
			parts.push(part);
			i = j + 2;
			++j;
		}
	}
	
	if (parts.length == 0)
		this.inversePartial(stack, tokens, parseFloat(value));
	else {
		var accumulatedvalue = 0;
		var valueindex = value.length - 1;
		for (var i = parts.length - 1; i >= 0; --i) {
			if (parts[i].separator == value.charAt(valueindex)) {
				var lastvalueindex = valueindex;
				for (--valueindex; valueindex >= 0; --valueindex) {
					if (isNaN(value.charAt(valueindex)))
						break;
				}
				var partialvalue = parseFloat(value.substring(valueindex + 1, lastvalueindex));
				accumulatedvalue += partialvalue;				
			}
			
			if (i > 0) {
				this.inversePartial(stack, parts[i].expr, accumulatedvalue);
				this.assert(stack.length == 1, "unexpected result #{stacklength}", { stacklength: stack.length });
				accumulatedvalue = stack.pop();
			}
			else
				this.inversePartial(stack, parts[i].expr, accumulatedvalue);				
		}		
	}

	this.assert(stack.length == 1, "unexpected result #{stacklength}", { stacklength: stack.length });
	return stack.pop();
}

Postfix.prototype.convert = function(expr, sivalue) {
	var precision = settingInformation.precision;

	var tokens = this.tokenize(expr);
	var stack = new Array();
	var formatted = "";
	
	for (var i = 0; i < tokens.length; ++i) {
		var token = tokens[i];
		if (this.isValueToken(token))
			stack.push(token);
		else if (this.isOperatorToken(token))
			this.evaluateOperator(stack, token);
		else if (this.isSeperatorToken(token)) {
			this.assert(i + 1 < tokens.length, "seperator is missing #{tokenlength}", { tokenlength: tokens.length });
			this.assert(stack.length == 1, "stack is not empty before a seperator", {});
			
			var val = stack.pop();
			var last = i + 1 + 1 == tokens.length;
			var taken = last ? val.toFixed(precision) : Math.floor(val);
			formatted += taken;
			formatted += tokens[++i];
			var remaining = val - taken;
			stack.push(remaining);
		}
		else {
			// Assume this is always the SI value
			stack.push(sivalue);
		}
	}
	
	if (formatted.length > 0)
		return formatted;
	this.assert(stack.length == 1, "unexpected result #{stacklength}", { stacklength: stack.length });		
	return stack.pop().toFixed(precision);
}

Postfix.prototype.tokenize = function(expr) {
	var tokens = expr.split(" ");
	return tokens;
}

Postfix.prototype.isValueToken = function(token) {
	if (isNaN(token))
		return false;
	return true;
}

Postfix.prototype.isSeperatorToken = function(token) {
	if (token == ";")
		return true;
	return false;
}

Postfix.prototype.isOperatorToken = function(token) {
	switch (token) {
		case "+":
		case "-":
		case "*":
		case "/":
			return true;
	}
	return false;
}

Postfix.prototype.isUnitTokenOperator = function(stack) {
	var numop = 2;
	this.assert(stack.length >= 2, "stack underflow #{stacklength}", { stacklength: stack.length });

	for (var i = 0; i < numop; ++i) {
		var token = stack[stack.length - numop + i];
		if (!this.isValueToken(token))
			return true;
	}
	return false;
}

Postfix.prototype.evaluateOperator = function(stack, operator) {
	// Assume that every operator takes two arguments.
	var numargs = 2;
	this.assert(stack.length >= numargs, "stack underflow #{stacklength} < #{required}", { stacklength: stack.length, required: numargs });
	var op2 = parseFloat(stack.pop());
	var op1 = parseFloat(stack.pop());

	var result;
	switch (operator) {
		case "+": result = op1 + op2; break;
		case "-": result = op1 - op2; break;
		case "*": result = op1 * op2; break;
		case "/": result = op1 / op2; break;
		default:
			this.assert(false, "unrecognized operator #{token}", { token: operator });
			break;
	}
	stack.push(result);
}

Postfix.prototype.evaluateInverseOperator = function(stack, operator, curval) {
	// Assume that every operator takes two arguments.
	var numargs = 2;
	var op1, op2;
	if (stack.length == numargs - 1) {
		op2 = stack.pop();
		op1 = null;
	}
	else {
		this.assert(stack.length >= numargs, "stack underflow #{stacklength} < #{required}", { stacklength: stack.length, required: numargs });
		op2 = stack.pop();
		op1 = stack.pop();
	}

	var lop, rop;
	if (op1 == null) {
		lop = curval;
		rop = parseFloat(op2);
	}
	else if (this.isOperatorToken(op2)) {
		this.assert(this.isValueToken(op1), "operand 1 is not a value #{operand1}", { operand1: op1 });
		lop = parseFloat(op1);
		rop = curval;
		stack.push(op2);
	}
	else if (this.isOperatorToken(op1)) {
		this.assert(this.isValueToken(op2), "operand 2 is not a value #{operand2}", { operand2: op2 });
		lop = curval;
		rop = parseFloat(op2);
		stack.push(op1);
	}
	else {
		this.assert(false, "Neither operand 1 nor operand2 is an operator", { });
	}

	var result;
	switch (operator) {
		case "+": result = lop - rop; break;
		case "-": result = lop + rop; break;
		case "*": result = lop / rop; break;
		case "/": result = lop * rop; break;
		default:
			this.assert(false, "unrecognized operator #{token}", { token: operator });
			break;
	}
	return result;
}

